General Remarks
- We suggest to read the following tutorials before you start implementing:
- Group work is NOT allowed in the
lab. You have to work alone. Discussions with colleagues (e.g., in the
forum) are allowed but the code has to be written alone.
- Be sure to check the Tricky Parts section for questions!
Submission Guide (for all Labs)
Submission
- You must upload your solution using the Teaching Tool before the submission deadline: 04.12., 18:00 cet.
- the deadline is hard! You are responsible for submitting your
solution in time. If you do not submit, you won't get any points!
- Upload your solution as a ZIP file. Please
submit only the sources including your build and properties file (see
below) of your solution (not the compiled class files and no
third-party libraries).
- Your submission must compile and run in our lab environment. Use and complete the provided ant template.
- Before the submission deadline, you can upload your solution as often as you like. Note that any existing submission will be replaced by uploading a new one.
- Please make sure that your upload was successful (i.e., you
should be able to download your solution - as the tutors will do during
the interview).
Interviews
- After the submission deadline, there will be a mandatory
interview (Abgabegespräch). You must register for a time slot to the
interviews using the Teaching Tool.
- You can do the interview only if you submitted your solution before the deadline!
- The interview will take place in the DSLab. During the interview, you will be asked about the solution that you uploaded (i.e., changes after the deadline will not be taken into account!). In the interview you need to explain your code, design and architecture in detail.
- Remember that you can do the interview only once!
Description
In this assignment you will learn:
- the basics of a simple distributed object technology (RMI)
- how to bind and lookup objects with a naming service
- how to implement callbacks with RMI
Overview
In this assignment we will develop a simplified messenger
application (like ICQ or MSN Messenger), which allows users to find
other users, become friends and exchange messages.
First, here’s a short explanation of the domain:
- For each user the following master data is
captured: username, password, first name, last name, birthday,
profession, email address and hobbies. The username must be unique
among all users.
- Users may establish friendships. For
friendships there always must be two agreeing parties, so one user has
to send a friendship request to another user, who may then accept or
reject it. Friendships allow users to send messages to each other; also
friends of a user are notified when he/she goes online or offline or
when he unregisters. Users may also stop being friends.
- The public user data (i.e. the data that may
be inspected by other users) contains the whole user master data
(except the password), the usernames of all friends and the current
connection state (i.e. online/offline) and the time the user was last
online.
For this assignment we use a conventional client-server architecture
as shown in Figure 1. There is one central server who’s responsible for
managing the user information. For each operation (shown using solid
lines) the clients have to contact the server, which implements the
required behavior. Hence, in contrast to Lab1 there’s no direct
user-to-user communication – every operation is propagated via the
server.
 |
| Figure 1: Messenger Architecture |
Most operations invoked on the server lead to
notifications (shown using dashed lines) of other users, for instance
login or send messages. These event notifications will be implemented
using RMI callbacks. If the recipients of any event (e.g., friendship
request/acceptation, incoming messages, etc.) are offline, these events
have to be preserved until the users go online the next time. Then the
server has to deliver all missed events.
Most distributed object frameworks provide a naming
service, which allows binding/looking up remote references to/by simple
names. By this the coupling between clients (looking up) and servers
(binding) can be reduced, and the real location of the server object
becomes transparent. RMI provides the java.rmi.registry.Registry
service (which itself is a Java RMI Remote interface) to accomplish
these features. Therefore you’ll have to bind your remote server object
to the registry, and look it up in your client.
Finally, the state of the server must not be lost
when shutting down normally, so the information must be written to the
file system.
Server
Arguments
Your server program does not need any command line arguments this time. Instead a properties file (named messenger.properties) should be read in from the classpath (see the hint section for details). The properties file is provided and can be downloaded here. It contains the port where a registry service has to be started, as well as the name that should be used for binding the server object. Finally the file name in which to store the data persistently is provided.
Messenger remote interface
In RMI you always have to define remote interfaces (interfaces extending java.rmi.Remote). Each method of this interface must throw java.rmi.RemoteException,
so errors occurring during the remote invocation can be signaled to the
caller. Define your server interface according to the description of
the client interactive commands.
Implementation details
On startup you first have to read in the properties file. If the
storage file with the given file name exists, you have to read it and
restore the server state. See the hint section
for help on how to do this. Then create a registry service, export your
server object to make it remotely available and finally bind it to the
specified name in the registry (help can be found here).
As in Lab1 the server has to manage state across
several threads. However, this time you don’t have to create the
threads yourself. Instead the RMI runtime takes over this job and
transparently uses threads from a thread pool to allow concurrent
access to your remote object. This again makes synchronization
necessary when accessing your shared data structures!
Note that users may go offline without informing the
server appropriately (for example if the computer crashes or the
connection is lost). You don’t have to detect this at once, but the
next time the server is trying to contact the user (for example to
deliver a message): if a java.rmi.RemoteException
occurs you may assume the user is offline. Therefore update the user’s
state, store the event for the user and inform his/her friends about
it.
Your server must also be able to cope with invalid
requests, therefore check that each parameter of each method is not
null, and otherwise throw a java.lang.IllegalArgumentException.
As you will later see the most operations require a logged in user,
thus you have to make sure that the caller is really logged in and is
whom he/she’s pretending to be (either by using a session identifier or
by letting the client submit its user name and password for each
operation). Also take care in your server implementation to deal with
semantically invalid requests (such as cancelling a non existing
friendship, accepting a non existing friendship request, unknown login
credentials etc).
The server is responsible for firing notifications if certain events occur (you find the concrete list of events here).
If some recipients are not online the server must queue their events
(except for "Friend went online/offline" events), and finally inform
these users when they login the next time.
If your server is ready for handling requests print “Server up. Hit enter to exit.”
to the console and implement this behavior. On exit unbind the remote
object from the registry and also unexport it, otherwise your program
might not shut down – again you must not use System.exit().
Do not forget to store the server’s state into the specified file. If
this file does not exist yet, you have to create it (see hints for a little help).
Client
In this part you have to build an interactive command line client application.
Arguments
Again no arguments are specified on the command line; the client has to read in the same properties file as the server (messenger.properties) from the classpath. You will need the host name and port where the registry service is listening (started by the server) and the binding name of the server object.
Callback messenger remote interface
The server quite often needs to notify the clients about certain
events. The server interface you are going to specify only allows
clients to contact the server synchronously, but how about notifying
the clients about asynchronous events such as a sent message? Polling
would be an option, but a very inconvenient one. So the way to go is to
define a remote interface for clients, of which the server itself can
call methods remotely.
Therefore
you have to provide one or several methods that can be called by the
server to inform the client about the different events. To sum it up,
these events may occur:
- Friendship requested/accepted/rejected/cancelled
- Message from friend
- Friend went offline/online
- Friend unregistered
If any of these events occurs you have to
print an appropriate message containing all the relevant information
(including the time of occurrence) to the console.
Implementation details
On startup of your program you first have to read in the properties
file. Then you have to obtain a reference to the registry service and
look up the server object with the specified name. Note that you can
only cast the looked up java.rmi.Remote instance to the
remote interface, not the implementing class, because only a stub,
which is responsible for communicating with the real server object, is
returned!
Now that we’ve got a reference to our server, the
user may enter commands (see below). However, note that you have to
implement the callback remote interface and export it like you exported
your server object. The only difference is that you should not bind it
to the registry, but instead simple pass it as parameter for the login
command. Since there are quite a lot of different commands, we
recommend using the Command pattern, which is described fairly well in this article.
Interactive commands
The following commands must be supported by your client application.
Take care of handling invalid commands and arguments and provide usage
messages in these cases. Print meaningful error messages whenever the
server throws an “expected” exception (such as “Username already exists.” when trying to register with an existing username). Also print success messages if an operation could be executed successfully.
For all commands you first need to login (except for register, login and stop).
register <userName>
<password> <firstName> <lastName> <birthday in
format dd.MM.yyyy> <profession> <emailAddress> {hobby}
Registers the user at the server. {hobby}
describes an optional list of hobbies, where each hobby is exactly one
word. You may assume syntactically correct email addresses. However,
the server has to check that no other user with the provided username
yet exists.
E.g.:
:> register user1 password1 FirstName1 LastName1 01.01.1985 student user1@user1.at jogging swimming
Registration was successful.
unregister
Unregisters the currently logged in user from the server. The server
has to take care of informing all friends about the unregistration. On
the client side the user must also be logged out afterwards (and the
remote callback object unexported).
login <username> <password>
Logs the user in. In your client implementation this is the time to
create your callback interface implementation object, export it and
pass it as parameter to the server. The server has to return the user’s
data (name, birthday, profession, email address and hobbies), his/her
direct friends’ public data, pending friendship requests (outgoing as
well as incoming friendship requests that have not been accepted or
rejected yet) and finally all missed events (see here).
Any friend must be informed by the server that the user has gone online
now. The output should include the same information as displayed below:
:> login user1 password1
Your data:
Name: FirstName1 LastName1
Birthday: 01.01.1985
Profession: student
Email: user1@user1.at
Hobbies:
jogging
swimming
Friends:
user5 online!
user6 offline (last online: 05.10.2008 @ 19:06)
Outgoing friendships requests:
user3 since 08.10.2008 @ 13:45
Incoming friendships requests:
user4 since 08.10.2008 @ 13:41
Events since last logout:
08.10.2008 @ 13:41:10 | user4 requested friendship.
08.10.2008 @ 13:44:41 | user5: Contact me asap!
logout
Logs
out the currently logged in user. Any friend must be informed by the
server that the user has gone offline. In your client implementation
you have to unexport your previously exported callback remote object.
findUser [searchString]
Queries the server for finding all users matching the search string.
Matching in this context means that the search string has to be
case-insensitively part of the user name, first name, last name,
profession, email address or any hobby. If searchString is omitted, all
registered users must be found. The server then has to return the
public data of all found users. Your output should include the same
information as displayed below:
:> findUser user
user1:
Name: FirstName1 FirstName2
Birthday: 01.01.1985
Profession: student
Email: user1@user1.at
Hobbies:
jogging
swimming
Friends:
user5
user6
State: offline, last online on: 08.10.2008 @ 09:00
getUserInfo <userName>
Gets the information of a specific user. The server has to return the
user’s public data and the output should contain the same data as
above.
requestFriend <userName>
Requests the friendship of another user. The server has to check they
are not friends yet and that the other user has not requested
friendship yet. Finally let the server inform the recipient of this
request about this event.
acceptFriend <userName>
Accepts the requested friendship; the server has to inform the
requestor appropriately. In your server you have to check that such a
request really exists.
rejectFriend <userName>
Rejects the requested friendship; the server has to inform the
requestor appropriately. In your server you have to check that such a
request really exists.
cancelFriend <userName>
Cancels an existing friendship with the specified user. The server has to notify the other user about this event.
sendMsg <userName> “<message>”
Sends a message to the specified friend. The message must start and end
with a quote – all characters in between (without the quotes) have to
be transmitted to the server. The server has to check that the
recipient is really a friend and then forwards the message to it.
stop
Stops the client application. If a user is currently logged in you have to log it out implicitly. Again you may not use System.exit(), but have to orderly close all acquired resources.
Lab port policy
Since each student has to start its own registry service (which
requires an unused port for listening for requests), we have to make
sure that each student uses its own port (this has to be adjusted in messenger.properties).
So if you are testing your solution in the lab environment (i.e. on the lab servers) you have to obey the following rule:
you may only use the port 10.000 + dslabXXX * 10 for the registry. So if your account is dslab250 you have to use the port 12500.
As
you might have thought of your remote objects also require an unused
port. But since we are using the registry for mediation purposes, this
can be an anonymous (any available port selected by the operating
system) port (see exporting objects for more details).
Ant template
As in Lab1 we provide a template build file (build.xml) in which you only have to adjust some class names. Put your source into the subdirectory "src", place the messenger.properties file into the "src"
directory (the ant compile task then copies this file to the build
directory) move on the command line to the directory where the build
file is located and simply type "ant" for compilation. Type "ant run-server" to start the server, "ant run-client" to start a client.
Put the src directory including messenger.properties and build.xml into your submission.
Note that it's absolutely required that we are able to start your programs with these predefined commands!
Hints & Tricky Parts
Further Reading Suggestions